import urllib.parse
import hashlib
import secrets
import time
from datetime import datetime
from django.http import JsonResponse, HttpResponseRedirect
import requests
import json
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import User
from utils import tooljwt
from utils.verify_code import generate_verify_code
from utils.ronglian import send_sms_by_requests
from utils.redis import redistool  # 导入原始工具类
from django.contrib.auth import login
from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import redirect
import base64
import hmac
from hashlib import sha256
import re

# 初始化 JWT 工具
jwt = tooljwt.JwtTool()


def generate_signature(user_id, username):
    """生成签名并保存到数据库"""
    timestamp = str(int(time.time()))
    signature_data = f"{user_id}_{username}_{timestamp}_{secrets.token_urlsafe(16)}"
    signature = hashlib.sha256(signature_data.encode()).hexdigest()
    return signature


# 账密登录视图
class LoginView(APIView):
    def post(self, request):
        username = request.data.get("username")
        password = request.data.get("password")

        if not username or not password:
            return Response({
                "message": "用户名或密码不能为空",
                "code": 400
            })

        # 查询用户（支持手机号/邮箱/用户名）
        user = None
        if username.isdigit() and len(username) == 11:
            user = User.objects.filter(phone=username).first()
        elif "@" in username:
            user = User.objects.filter(email=username).first()
        else:
            user = User.objects.filter(username=username).first()

        if user and user.check_password(password):
            token = jwt.gettoken({"id": user.id, "username": user.username})
            signature = generate_signature(user.id, user.username)
            user.Suppl_1 = signature
            user.save()
            return Response({
                "code": 200,
                "message": "登录成功",
                "data": {
                    "token": token,
                    "user_id": user.id,
                    "username": user.username,
                    "signature": signature
                }
            })
        else:
            return Response({
                "message": "用户名/密码错误或用户不存在",
                "code": 400
            })


# 获取验证码视图
class VerifyCodeLoginView(APIView):
    def post(self, request):
        phone = request.data.get("phone")
        try:
            # 1. 验证手机号格式（原有逻辑）
            if not re.match(r'^1[3-9]\d{9}$', phone):
                return JsonResponse({"code": 400, "msg": "手机号格式错误"})

            # 2. 生成验证码（原有逻辑）
            code = generate_verify_code()

            # 3. Redis 操作（添加 try-except）
            key = f"verify_code:{phone}"
            redistool.set_code(key, code)
            redistool.redis.expire(key, 600)

            # 4. 防刷限制（原有逻辑）
            limit_key = f"send_limit:{phone}"
            if redistool.redis.get(limit_key):
                return JsonResponse({"code": 400, "msg": "发送过于频繁，请1分钟后再试"})
            redistool.redis.setex(limit_key, 60, 1)

            # 5. 调用发送短信函数
            if send_sms_by_requests(phone, code):
                print(code)
                return JsonResponse({"code": 200, "msg": "验证码发送成功"})
            else:
                # 清理缓存
                redistool.redis.delete(key)
                redistool.redis.delete(limit_key)
                return JsonResponse({"code": 500, "msg": "验证码发送失败"})
        except Exception as e:
            print(f"登录接口异常：{str(e)}")
            import traceback
            traceback.print_exc()
            return JsonResponse({"code": 500, "msg": "服务器内部错误"})


# 验证码登录视图
class RegisterView(APIView):
    def post(self, request):
        phone = request.data.get("phone")
        code = request.data.get("code")

        # 1. 验证参数
        if not all([phone, code]):
            return JsonResponse({"code": 400, "msg": "参数不完整"})

        # 2. 获取缓存的验证码
        key = f"verify_code:{phone}"
        cached_code = redistool.get_code(key)
        print(cached_code)

        # 3. 验证验证码
        if not cached_code:
            return JsonResponse({"code": 400, "msg": "验证码已过期"})
        if code != cached_code:
            return JsonResponse({"code": 400, "msg": "验证码错误"})

        # 4. 查找或创建用户
        try:
            user = User.objects.get(phone=phone)
        except User.DoesNotExist:
            user = User.objects.create_user(username=phone, phone=phone)

        # 5. 生成登录凭证（与账密登录保持一致）
        token = jwt.gettoken({"id": user.id, "username": user.username})
        signature = generate_signature(user.id, user.username)
        user.Suppl_1 = signature
        user.save()

        # 6. 登录并删除验证码
        login(request, user)
        redistool.redis.delete(key)

        # 7. 返回登录成功结果（与账密登录格式保持一致）
        return JsonResponse({
            "code": 200,
            "msg": "登录成功",
            "data": {
                "token": token,
                "user_id": user.id,
                "username": user.username,
                "signature": signature
            }
        })


# 钉钉登录核心配置（统一管理）
DINGTALK_CONFIG = {
    "APP_KEY": settings.DINGTALK.get("APP_KEY"),
    "APP_SECRET": settings.DINGTALK.get("APP_SECRET"),
    "REDIRECT_URI": settings.DINGTALK.get("REDIRECT_URI"),
    "FRONTEND_URL": settings.DINGTALK.get("FRONTEND_URL"),
    "CODE_EXPIRE_MINUTES": 5  # 钉钉 code 有效期（分钟）
}


# 1. 构建钉钉登录 URL（唯一入口）
def ding_url(request):
    """生成钉钉扫码登录链接，前端跳转至此获取 code"""
    appid = DINGTALK_CONFIG["APP_KEY"]
    redirect_uri = urllib.parse.quote(DINGTALK_CONFIG["REDIRECT_URI"])  # 仅对回调地址编码
    login_url = (
        f"https://oapi.dingtalk.com/connect/qrconnect"
        f"?appid={appid}"
        f"&response_type=code"
        f"&scope=snsapi_login"
        f"&state=STATE"
        f"&redirect_uri={redirect_uri}"
    )
    return redirect(login_url)


# 2. 钉钉回调处理（仅用于接收 code 并转发给接口）
def ding_back(request):
    """钉钉回调页面，接收 code 后转发给 handle_dingcode 接口"""
    code = request.GET.get('code')
    frontend_url = DINGTALK_CONFIG["FRONTEND_URL"]

    if not code:
        # 无 code 时跳转回登录页并提示错误
        error = request.GET.get('error', '未知错误')
        error_desc = request.GET.get('error_description', '')
        redirect_url = (
            f"{frontend_url}/login?error=dingtalk_failed"
            f"&code={urllib.parse.quote(error)}"
            f"&msg={urllib.parse.quote(error_desc)}"
        )
        return HttpResponseRedirect(redirect_url)

    # 有 code 时，跳转至前端并携带 code（由前端调用接口处理）
    redirect_url = (
        f"{frontend_url}/dingtalk-login"
        f"?code={urllib.parse.quote(code)}"
        f"&generate_time={int(time.time())}"  # 携带 code 生成时间戳
    )
    return HttpResponseRedirect(redirect_url)


# 3. 处理钉钉临时 code 核心接口（唯一处理入口）
@csrf_exempt
def handle_dingcode(request):
    if request.method != "POST":
        return JsonResponse({"code": 405, "message": "仅支持POST请求"}, status=405)

    try:
        # 1. 解析请求参数
        data = json.loads(request.body)
        code = data.get("code")
        generate_time = data.get("generate_time")  # 前端传递的 code 生成时间戳

        if not code:
            return JsonResponse({"code": 400, "message": "缺少授权code"}, status=400)

        # 2. 校验 code 有效期（5分钟内）
        current_time = int(time.time())
        if generate_time:
            code_age = (current_time - int(generate_time)) / 60
            if code_age > DINGTALK_CONFIG["CODE_EXPIRE_MINUTES"]:
                return JsonResponse({
                    "code": 401,
                    "message": "授权码已过期，请重新扫码",
                    "error_type": "expired_code"
                }, status=401)

        # 3. 构造钉钉接口参数
        timestamp = str(int(round(time.time() * 1000)))  # 毫秒级时间戳
        app_secret = DINGTALK_CONFIG["APP_SECRET"]
        app_key = DINGTALK_CONFIG["APP_KEY"]

        # 4. 生成签名（严格按照钉钉规范：HMAC-SHA256 + Base64）
        def generate_dingtalk_signature(app_secret, timestamp):
            """严格按照钉钉规范生成签名"""
            # 1. 确保 app_secret 和 timestamp 是字符串
            app_secret_str = str(app_secret).strip()
            timestamp_str = str(timestamp).strip()
            # 2. HMAC-SHA256 加密
            hmac_obj = hmac.new(
                app_secret_str.encode("utf-8"),
                timestamp_str.encode("utf-8"),
                digestmod=sha256
            )
            # 3. Base64 编码（直接返回字符串，无二次编码）
            signature = base64.b64encode(hmac_obj.digest()).decode("utf-8")
            return signature

        signature = generate_dingtalk_signature(app_secret, timestamp)

        # 调用钉钉接口前打印参数（便于调试）
        print(f"[钉钉登录] 调试参数：")
        print(f"  APP_KEY: {app_key}")
        print(f"  APP_SECRET: {app_secret[:10]}...（隐藏部分内容）")
        print(f"  timestamp: {timestamp}（类型：{type(timestamp)}）")
        print(f"  signature: {signature}")
        print(f"  code: {code[:20]}...")

        # 5. 调用钉钉用户信息接口（signature需要URL编码）
        url = (
            f"https://oapi.dingtalk.com/sns/getuserinfo_bycode"
            f"?signature={urllib.parse.quote(signature)}"
            f"&timestamp={timestamp}"
            f"&accessKey={app_key}"
        )
        payload = {"tmp_auth_code": code}
        headers = {"Content-Type": "application/json"}

        # 延长超时时间，确保接口响应
        res = requests.post(url, data=json.dumps(payload), headers=headers, timeout=30)

        # 6. 处理接口响应
        if res.status_code != 200:
            return JsonResponse({
                "code": 500,
                "message": f"钉钉接口异常，状态码：{res.status_code}"
            }, status=500)

        res_dict = res.json()
        print("钉钉接口返回：", res_dict)

        # 7. 处理钉钉错误码
        err_code = res_dict.get("errcode", -1)
        if err_code != 0:
            err_msg = res_dict.get("errmsg", "未知错误")
            # 针对 853004 签名验证失败错误
            if err_code == 853004:
                return JsonResponse({
                    "code": 400,
                    "message": "签名验证失败，请检查服务器时间和钉钉配置（APP_KEY/APP_SECRET）",
                    "error_type": "signature_failed",
                    "error_code": 853004
                }, status=400)
            # 针对 40078 错误单独处理
            if err_code == 40078:
                return JsonResponse({
                    "code": 401,
                    "message": "授权码无效或已过期，请重新扫码登录",
                    "error_type": "invalid_code"
                }, status=401)
            return JsonResponse({
                "code": 400,
                "message": f"钉钉接口错误：[{err_code}] {err_msg}"
            }, status=400)

        # 8. 提取 openid（兼容两种返回格式）
        openid = None
        if "openid" in res_dict:
            openid = res_dict["openid"]
        elif "user_info" in res_dict:
            openid = res_dict["user_info"].get("openid")

        if not openid:
            return JsonResponse({
                "code": 400,
                "message": "钉钉接口未返回用户唯一标识（openid）"
            }, status=400)

        user_info = res_dict.get("user_info", {})

        # 9. 关联或创建用户
        user, created = User.objects.get_or_create(
            dingtalk_openid=openid,
            defaults={
                "username": user_info.get("nick"),
                "phone": f"dingtalk_{openid}"[:11],
                "email": f"dingtalk_{openid}@example.com",
                "is_active": True
            }
        )
        if created:
            user.set_unusable_password()  # 钉钉用户无需密码
            user.save()

        # 10. 生成登录凭证
        token = jwt.gettoken({"id": user.id, "username": user.username})
        user_signature = generate_signature(user.id, user.username)
        user.Suppl_1 = user_signature
        user.save()

        # 11. 返回成功结果
        return JsonResponse({
            "code": 200,
            "message": "钉钉登录成功",
            "data": {
                "token": token,
                "user_id": user.id,
                "username": user.username,
                "openid": openid,
                "signature": user_signature
            }
        }, status=200)

    except json.JSONDecodeError:
        return JsonResponse({"code": 400, "message": "请求参数格式错误（需JSON）"}, status=400)
    except requests.exceptions.Timeout:
        return JsonResponse({"code": 500, "message": "调用钉钉接口超时，请重试"}, status=500)
    except requests.exceptions.RequestException as e:
        return JsonResponse({"code": 500, "message": f"网络错误：{str(e)}"}, status=500)
    except Exception as e:
        print(f"钉钉登录异常：{str(e)}")
        import traceback
        traceback.print_exc()
        return JsonResponse({"code": 500, "message": "服务器内部错误"}, status=500)